﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GenericGA.generationmaker;
using GenericGA.extrandom;
using GenericGA.reader;

namespace GenericGA
{
    /// <summary>
    /// A genetic algorithm which uses a generic chromosome representation to search for solutions to any problem.
    /// </summary>
    /// <typeparam name="P">The phenotype type, or type of the candidate solutions.</typeparam>
    public class GA<P>
    {

        /// <summary>
        /// Delegate for phenotype building functions which turn a reader object into a phenotype.
        /// </summary>
        /// <param name="reader">Reader object which provides methods to read the chromosome of individuals.</param>
        /// <returns>The phenotype, or candidate solution.</returns>
        public delegate P PhenotypeBuildingFunctionType(Reader reader);

        /// <summary>
        /// Delegate for fitness function which turn a phenotype into a number where the greater the number the closer the phenotype is to the perfect solution.
        /// </summary>
        /// <param name="phenotype">The phenotype to evaluate the fitness of.</param>
        /// <returns>The fitness of the given phenotype.</returns>
        public delegate double FitnessFunctionType(P phenotype);

        /// <summary>
        /// A random number generator.
        /// </summary>
        private readonly ExtRandom randGen;

        /// <summary>
        /// The phenotype building function to be used to create new individuals.
        /// </summary>
        private PhenotypeBuildingFunctionType phenotypeBuildingFunction;

        /// <summary>
        /// The fitness function to be used to select individuals.
        /// </summary>
        public FitnessFunctionType FitnessFunction { get; set; }

        /// <summary>
        /// The generation maker object which produces generations of individuals.
        /// </summary>
        public GenerationMaker<P> GenerationMaker { get; set; }

        /// <summary>
        /// The population of the current generation of individuals. You cannot modify this list, you only get a copy of the original.
        /// </summary>
        public List<Individual<P>> Population
        {
            get
            {
                return new List<Individual<P>>(population);
            }
        }
        private List<Individual<P>> population;

        /// <summary>
        /// The best individual in the last generation's population.
        /// </summary>
        public Individual<P> BestIndividual { get; private set; }

        /// <summary>
        /// The longest chromosome in the population. Could be used to vary the mutation rate.
        /// </summary>
        public int MaxChromosomeLength { get; private set; }

        /// <summary>
        /// Constructor to create a new genetic algorithm.
        /// </summary>
        /// <param name="randGen">A random number generator.</param>
        /// <param name="phenotypeBuildingFunction">The phenotype building function to be used to create new individuals.</param>
        /// <param name="fitnessFunction">The fitness function to be used to select individuals.</param>
        /// <param name="generationMaker">The generation maker object which produces generations of individuals.</param>
        /// <param name="initialRandomPopulationSize">Number of random individuals to use as an initial population.</param>
        public GA(ExtRandom randGen, PhenotypeBuildingFunctionType phenotypeBuildingFunction, FitnessFunctionType fitnessFunction, GenerationMaker<P> generationMaker, int initialRandomPopulationSize)
        {
            this.randGen = randGen;
            this.phenotypeBuildingFunction = phenotypeBuildingFunction;
            this.FitnessFunction = fitnessFunction;
            this.GenerationMaker = generationMaker;
            this.population = new List<Individual<P>>();

            BestIndividual = null;
            MaxChromosomeLength = 0;
            for (int i = 0; i < initialRandomPopulationSize; i++)
            {
                AddIndividual(new List<ulong>());
            }
        }

        /// <summary>
        /// Constructor to create a new genetic algorithm using a default generation maker.
        /// </summary>
        /// <param name="randGen">A random number generator.</param>
        /// <param name="phenotypeBuildingFunction">The phenotype building function to be used to create new individuals.</param>
        /// <param name="fitnessFunction">The fitness function to be used to select individuals.</param>
        /// <param name="initialRandomPopulationSize">Number of random individuals to use as an initial population.</param>
        public GA(ExtRandom randGen, PhenotypeBuildingFunctionType phenotypeBuildingFunction, FitnessFunctionType fitnessFunction, int initialRandomPopulationSize)
            : this(randGen, phenotypeBuildingFunction, fitnessFunction, new TraditionalEvolution<P>(randGen, initialRandomPopulationSize), initialRandomPopulationSize)
        {
        }

        /// <summary>
        /// Add a new individual to the current generation's population.
        /// </summary>
        /// <param name="chromosome">The chromosome of the individual.</param>
        public void AddIndividual(List<ulong> chromosome)
        {
            Individual<P> individual = new Individual<P>(randGen, phenotypeBuildingFunction, chromosome);
            population.Add(individual);

            individual.RefreshFitness(FitnessFunction);
            if (BestIndividual == null || individual.CachedFitness > BestIndividual.CachedFitness)
            {
                BestIndividual = individual;
            }
            if (individual.ChromosomeLength() > MaxChromosomeLength)
            {
                MaxChromosomeLength = individual.ChromosomeLength();
            }
        }

        /// <summary>
        /// Move one step forward in the evolution process to search for better solutions.
        /// The current generation's population will be changed to be made of the offspring of the current generation.
        /// </summary>
        public void NextGeneration()
        {
            List<List<ulong>> newGeneration = GenerationMaker.NextGeneration(population, phenotypeBuildingFunction, FitnessFunction);

            population.Clear();
            BestIndividual = null;
            MaxChromosomeLength = 0;
            foreach (List<ulong> chromosome in newGeneration)
            {
                AddIndividual(chromosome);
            }
        }

    }
}
